import os
import logging
from typing import List, Tuple, Optional
from contextlib import contextmanager

import paramiko
from sqlalchemy.orm import Session
from fastapi import HTTPException, Depends

from database import get_db
from model.cdn_config import CdnConfig
from model.certificate import Certificate
from utils.cert_util import apply_certificate_by_webroot
from utils.dns_util import DNSUtil
from services import certificate
from datetime import datetime

logger = logging.getLogger("fast-run")


class SSHConnection:
    """SSH连接管理器，统一处理SSH连接和操作"""

    def __init__(self, host: str, user: str = "root"):
        self.host = host
        self.user = user
        self.ssh = None
        self.sftp = None

    @contextmanager
    def connect(self):
        """SSH连接上下文管理器"""
        self.ssh = paramiko.SSHClient()
        self.ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())

        try:
            self.ssh.connect(hostname=self.host, username=self.user)
            self.sftp = self.ssh.open_sftp()
            yield self
        except Exception as e:
            logger.error(f"连接到 {self.host} 失败: {e}")
            raise
        finally:
            if self.sftp:
                self.sftp.close()
            if self.ssh:
                self.ssh.close()

    def execute_command(self, command: str) -> Tuple[str, str]:
        """执行SSH命令"""
        try:
            stdin, stdout, stderr = self.ssh.exec_command(command)
            return stdout.read().decode().strip(), stderr.read().decode().strip()
        except Exception as e:
            logger.error(f"在 {self.host} 执行命令失败: {command}, 错误: {e}")
            raise

    def create_directory(self, dir_path: str):
        """创建远程目录"""
        self.execute_command(f"sudo mkdir -p {dir_path}")

    def write_file(self, file_path: str, content: str):
        """写入远程文件"""
        # 确保目录存在
        dir_path = os.path.dirname(file_path)
        if dir_path:
            self.create_directory(dir_path)

        with self.sftp.file(file_path, "w") as f:
            f.write(content)
        logger.info(f"文件写入成功: {self.host}:{file_path}")


class NginxConfigTemplate:
    """Nginx配置模板管理"""

    @staticmethod
    def load_template(template_path: str) -> str:
        """加载模板文件"""
        try:
            with open(template_path, encoding="utf8") as f:
                return f.read()
        except FileNotFoundError:
            logger.error(f"模板文件未找到: {template_path}")
            raise HTTPException(status_code=500, detail=f"模板文件未找到: {template_path}")

    @staticmethod
    def render_template(template: str, domain: str, source_ip: str) -> str:
        """渲染模板"""
        return template.replace("<dns-name>", domain).replace("<source-ip>", source_ip)


class CdnConfigService:

    @classmethod
    def _deploy_to_single_node(cls, host: str, domain: str, nginx_conf: str,
                               nginx_ssl_conf: str, fullchain: str, privkey: str) -> bool:
        """部署到单个节点"""
        try:
            with SSHConnection(host).connect() as conn:
                # 创建缓存目录
                conn.create_directory(f"/var/cache/nginx/{domain}")

                # 部署证书
                cert_dir = f"/etc/letsencrypt/live/{domain}"
                conn.write_file(f"{cert_dir}/fullchain.pem", fullchain)
                conn.write_file(f"{cert_dir}/privkey.pem", privkey)

                with open("template/fullchain.pem") as fr:
                    ssl_fullchain = fr.read()
                with open("template/privkey.pem") as fr:
                    ssl_privkey = fr.read()
                ssl_cert_dir = f"/etc/letsencrypt/live/self"
                conn.write_file(f"{ssl_cert_dir}/fullchain.pem", ssl_fullchain)
                conn.write_file(f"{ssl_cert_dir}/privkey.pem", ssl_privkey)

                # 部署nginx配置
                nginx_conf_path = f"/etc/nginx/conf.d/{domain}.conf"
                nginx_ssl_conf_path = f"/etc/nginx/conf.d/ssl_default.conf"
                conn.write_file(nginx_conf_path, nginx_conf)
                conn.write_file(nginx_ssl_conf_path, nginx_ssl_conf)

                # 测试并重载nginx
                cls._test_and_reload_nginx(conn)

                logger.info(f"节点 {host} 部署成功")
                return True

        except Exception as e:
            logger.error(f"节点 {host} 部署失败: {e}")
            return False

    @classmethod
    def _test_and_reload_nginx(cls, conn: SSHConnection):
        """测试nginx配置并重载"""
        out, err = conn.execute_command("sudo nginx -t")
        if err and "test is successful" not in err:
            raise Exception(f"Nginx配置测试失败: {err}")

        # 检查nginx进程是否运行
        out, err = conn.execute_command("ps aux | grep nginx | grep -v grep | wc -l")
        nginx_count = int(out.strip()) if out.strip().isdigit() else 0

        if nginx_count > 0:
            # nginx正在运行，重载配置
            conn.execute_command("sudo nginx -s reload")
            logger.info(f"Nginx在 {conn.host} 重载成功")
        else:
            # nginx未运行，启动nginx
            logger.info(f"Nginx在 {conn.host} 未运行，正在启动...")
            out, err = conn.execute_command("sudo nginx")
            # 验证启动是否成功
            out, err = conn.execute_command("ps aux | grep nginx | grep -v grep | wc -l")
            nginx_count = int(out.strip()) if out.strip().isdigit() else 0

            if nginx_count > 0:
                logger.info(f"Nginx在 {conn.host} 启动成功")
            else:
                raise Exception(f"Nginx在 {conn.host} 启动失败: {err}")

    @classmethod
    def deploy_nginx(cls, domain: str, source_ip: str, deploy_nodes: List[str],
                     fullchain: str, privkey: str) -> List[dict]:
        """部署nginx配置到所有节点"""
        logger.info(f"开始部署nginx到 {len(deploy_nodes)} 个节点")

        # 加载和渲染模板
        nginx_template = NginxConfigTemplate.load_template("template/nginx.conf")
        ssl_template = NginxConfigTemplate.load_template("template/ssl_default.conf")

        nginx_conf = NginxConfigTemplate.render_template(nginx_template, domain, source_ip)
        nginx_ssl_conf = NginxConfigTemplate.render_template(ssl_template, "self", "self")

        # 部署到所有节点
        results = []
        success_count = 0

        for host in deploy_nodes:
            success = cls._deploy_to_single_node(
                host, domain, nginx_conf, nginx_ssl_conf, fullchain, privkey
            )
            results.append({"host": host, "success": success})
            if success:
                success_count += 1

        logger.info(f"部署完成: {success_count}/{len(deploy_nodes)} 个节点成功")

        if success_count == 0:
            raise HTTPException(status_code=500, detail="所有节点部署失败")
        elif success_count < len(deploy_nodes):
            logger.warning(f"部分节点部署失败: {len(deploy_nodes) - success_count} 个节点失败")

        return results

    @classmethod
    def _validate_deployment_requirements(cls, cdn_config: CdnConfig, db: Session) -> Tuple[
        str, str, str, List[str], str]:
        fullchain = ""
        privkey = ""
        """验证部署前置条件并返回必要参数"""
        # 获取证书
        if not DNSUtil.has_cname(cdn_config.domain, cdn_config.cname.domain):
            raise HTTPException(status_code=404,
                                detail=f"{cdn_config.domain} 没有 {cdn_config.cname.domain} 的CNAME记录")
        cert_db = db.query(Certificate).filter(Certificate.domain == cdn_config.domain).first()
        # TODO 多a记录申请证书会失败，改用新增时校验，没有证书，就去证书管理申请
        if not cert_db:
            raise HTTPException(status_code=404,
                                detail=f"{cdn_config.domain} 没有证书，请到证书管理中申请")
            # logger.warning("未找到对应证书, 开始自动申请")
            # apply_cert_host = cdn_config.cname.nodes[0].host
            # logger.info(f"{apply_cert_host}开始申请{cdn_config.domain}证书")
            # cert = certificate.apply_certificate(cdn_config.domain, apply_cert_host)
            # all_hosts = [i.host for i in cdn_config.cname.nodes]
            # cert = apply_certificate_by_webroot(cdn_config.domain, all_hosts)
            # if not cert:
            #     raise HTTPException(status_code=404, detail=f"申请证书失败,请联系管理员")
            # else:
            #     # 保存到数据库
            #     new_cert = Certificate(
            #         domain=cdn_config.domain,
            #         cert=getattr(cert, "cert", None),
            #         fullchain=cert['fullchain'],
            #         privkey=cert['privkey'],
            #         requested_at=datetime.now()  # 记录申请时间
            #     )
            #     db.add(new_cert)
            #     db.commit()
            #     db.refresh(new_cert)  # 可选，刷新获取最新状态
            #     logger.info("证书已成功保存")
            # fullchain = cert['fullchain']
            # privkey = cert['privkey']
        else:
            logger.info("存在证书，无需申请")
            fullchain = cert_db.fullchain
            privkey = cert_db.privkey
        domain = cdn_config.domain
        cname = cdn_config.cname
        origin_ip = cdn_config.originIp
        deploy_domain = cname.domain
        deploy_nodes = [node.host for node in cname.nodes]

        logger.info(f"开始部署: {domain} -> {deploy_domain}")
        logger.info(f"部署节点: {deploy_nodes}")

        # 验证CNAME配置
        if not DNSUtil.has_cname(domain, deploy_domain):
            raise HTTPException(
                status_code=400,
                detail=f"未配置CNAME到{deploy_domain}，请添加后再进行部署"
            )

        # 验证DNS解析
        if not DNSUtil.verify_domain(deploy_domain, deploy_nodes):
            raise HTTPException(
                status_code=400,
                detail="域名解析还未生效，请稍后再试"
            )

        return fullchain, privkey, domain, deploy_nodes, origin_ip

    @classmethod
    def deploy_cdn_config(cls, cdn_config_id: int, db: Session = Depends(get_db)) -> dict:
        """
        部署CDN配置到所有节点
        :param cdn_config_id: CDN配置ID
        :param db: 数据库会话
        :return: 部署结果
        """
        # 获取CDN配置
        cdn_config = db.query(CdnConfig).filter(CdnConfig.id == cdn_config_id).first()
        if not cdn_config:
            raise HTTPException(status_code=404, detail="CDN配置未找到")

        # 验证部署条件并获取参数
        fullchain, privkey, domain, deploy_nodes, origin_ip = cls._validate_deployment_requirements(cdn_config, db)

        # 执行部署
        logger.info("验证完成，开始部署")
        results = cls.deploy_nginx(
            domain=domain,
            source_ip=origin_ip,
            deploy_nodes=deploy_nodes,
            fullchain=fullchain,
            privkey=privkey
        )

        # 更新部署状态
        try:
            cdn_config.ifDeploy = True
            db.add(cdn_config)
            db.commit()
            db.refresh(cdn_config)
            logger.info(f"CDN配置 {cdn_config_id} 部署状态已更新")
        except Exception as e:
            logger.error(f"更新部署状态失败: {e}")
            # 不抛出异常，因为部署已经成功

        return {
            "status": "success",
            "message": f"CDN配置已部署到 {len([r for r in results if r['success']])}/{len(results)} 个节点",
            "results": results,
            "cdn_config": {
                "id": cdn_config.id,
                "is_deployed": cdn_config.ifDeploy
            }
        }


    @classmethod
    def _remove_from_single_node(cls, host: str, domain: str) -> bool:
        """从单个节点删除配置"""
        try:
            with SSHConnection(host).connect() as conn:
                # 删除nginx配置文件
                nginx_conf_path = f"/etc/nginx/conf.d/{domain}.conf"
                conn.execute_command(f"sudo rm -f {nginx_conf_path}")

                # 删除证书文件
                cert_dir = f"/etc/letsencrypt/live/{domain}"
                conn.execute_command(f"sudo rm -rf {cert_dir}")

                # 删除缓存目录
                cache_dir = f"/var/cache/nginx/{domain}"
                conn.execute_command(f"sudo rm -rf {cache_dir}")

                # 测试并重载nginx
                cls._test_and_reload_nginx(conn)

                logger.info(f"节点 {host} 配置删除成功")
                return True

        except Exception as e:
            logger.error(f"节点 {host} 配置删除失败: {e}")
            return False

    @classmethod
    def _remove_cache_from_single_node(cls, host: str, domain: str) -> bool:
        """从单个节点删除配置"""
        try:
            with SSHConnection(host).connect() as conn:

                # 删除缓存目录
                cache_dir = f"/var/cache/nginx/{domain}/*"
                conn.execute_command(f"sudo rm -rf {cache_dir}")

                # 测试并重载nginx
                cls._test_and_reload_nginx(conn)

                logger.info(f"节点 {host} 缓存删除成功")
                return True

        except Exception as e:
            logger.error(f"节点 {host} 缓存删除失败: {e}")
            return False


    @classmethod
    def remove_cdn_config(cls, cdn_config_id: int, db: Session = Depends(get_db)) -> dict:
        """
        删除CDN配置
        :param cdn_config_id: CDN配置ID
        :param db: 数据库会话
        :return: 删除结果
        """
        # 获取CDN配置
        cdn_config = db.query(CdnConfig).filter(CdnConfig.id == cdn_config_id).first()
        if not cdn_config:
            raise HTTPException(status_code=404, detail="CDN配置未找到")

        domain = cdn_config.domain
        cname = cdn_config.cname
        deploy_nodes = [node.host for node in cname.nodes]

        logger.info(f"开始删除CDN配置: {domain}")
        logger.info(f"删除节点: {deploy_nodes}")

        # 从所有节点删除配置
        results = []
        success_count = 0

        for host in deploy_nodes:
            success = cls._remove_from_single_node(host, domain)
            results.append({"host": host, "success": success})
            if success:
                success_count += 1

        logger.info(f"删除完成: {success_count}/{len(deploy_nodes)} 个节点成功")

        # 更新数据库状态
        try:
            cdn_config.ifDeploy = False
            db.add(cdn_config)
            db.commit()
            db.refresh(cdn_config)
            logger.info(f"CDN配置 {cdn_config_id} 部署状态已更新为未部署")
        except Exception as e:
            logger.error(f"更新部署状态失败: {e}")

        if success_count == 0:
            raise HTTPException(status_code=500, detail="所有节点配置删除失败")
        elif success_count < len(deploy_nodes):
            logger.warning(f"部分节点配置删除失败: {len(deploy_nodes) - success_count} 个节点失败")

        return {
            "status": "success",
            "message": f"CDN配置已从 {success_count}/{len(results)} 个节点删除",
            "results": results,
            "cdn_config": {
                "id": cdn_config.id,
                "is_deployed": cdn_config.ifDeploy
            }
        }

    @classmethod
    def clear_cache(cls, cdn_config_id: int, db: Session = Depends(get_db)) -> dict:
        """
        清除缓存
        :param cdn_config_id: CDN配置ID
        :param db: 数据库会话
        :return: 删除结果
        """
        # 获取CDN配置
        cdn_config = db.query(CdnConfig).filter(CdnConfig.id == cdn_config_id).first()
        if not cdn_config:
            raise HTTPException(status_code=404, detail="CDN配置未找到")

        domain = cdn_config.domain
        cname = cdn_config.cname
        deploy_nodes = [node.host for node in cname.nodes]

        logger.info(f"开始清除缓存: {domain}")
        logger.info(f"删除节点: {deploy_nodes}")

        # 从所有节点删除配置
        results = []
        success_count = 0

        for host in deploy_nodes:
            success = cls._remove_cache_from_single_node(host, domain)
            results.append({"host": host, "success": success})
            if success:
                success_count += 1

        logger.info(f"缓存删除完成: {success_count}/{len(deploy_nodes)} 个节点成功")
        if success_count == 0:
            raise HTTPException(status_code=500, detail="所有节点缓存删除失败")
        elif success_count < len(deploy_nodes):
            logger.warning(f"部分节点缓存删除失败: {len(deploy_nodes) - success_count} 个节点失败")

        return {
            "status": "success",
            "message": f"CDN缓存已从 {success_count}/{len(results)} 个节点删除",
            "results": results,
            "cdn_config": {
                "id": cdn_config.id,
                "is_deployed": cdn_config.ifDeploy
            }
        }